package com.rhit.rosehack.engine;

import java.awt.Color;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.ArrayList;
import java.util.Random;
import java.io.*;

import javax.swing.JOptionPane;

import com.rhit.rosehack.SQLObjects.Chunk;
import com.rhit.rosehack.SQLObjects.Database_Init;
import com.rhit.rosehack.SQLObjects.Item;
import com.rhit.rosehack.SQLObjects.Query;
import com.rhit.rosehack.SQLObjects.SQLObject;

/*
 * This is the main game engine, and will handle the calculations and running of the game.
 */
public class GameEngine {

	// Class variables
	private GUI thisGame;
	private Player myPlayer;
	private int currentFloor;
	private ArrayList<Chunk> floors;
	private boolean endPhase;
	private MapGenerator gen;

	// Initializes a new Game
	public GameEngine() {
		// Creates the GUI
		this.thisGame = new GUI();

		// Adds in the window listener
		thisGame.addWindowListener(new WindowAdapter() {
			public void windowClosing(WindowEvent e) {
				GameEngine.this.saveTables();

				System.exit(0);
			}
		});

		// Sets current floor, initializes stuff
		this.currentFloor = 0;
		this.floors = new ArrayList<Chunk>();

		this.gen = new MapGenerator();
		this.floors.add(new Chunk(this.currentFloor, new char[40][90],
				new ArrayList<Mob>()));

		initFromDatabase();

		this.refreshScreen();

	}

	// Places the player at the UP stairs
	private void placePlayerUp() {
		for (int x = 0; x < this.floors.get(currentFloor).getMap()[0].length; x++)
			for (int y = 0; y < this.floors.get(currentFloor).getMap().length; y++) {
				if (this.floors.get(currentFloor).getMap()[y][x] == '<') {
					this.myPlayer.setX(x);
					this.myPlayer.setY(y);
				}
			}

	}

	private void pickUpItem() {
		Item i = this.floors.get(currentFloor).itemAt(myPlayer.getX(),
				myPlayer.getY());

		if (i != null) {
			Query q = new Query();

			this.floors.get(currentFloor).removeItem(i);
			this.thisGame.placeCharacter(i.getX(), i.getY(), '.', Color.white);
			myPlayer.giveItem(i);

			System.out.println( "ITEM ID = " + i.getID() );
			
			q.pickupItem(i.getID(), myPlayer.getCreatureID());
		}
	}

	/*
	 * This is the standard game loop
	 */
	private void gameLoop() {
		// when next monster will spawn
		int nextSpawn = (int) (Math.random() * 50) + 1;
		int counter = 0;

		// GAME LOOOP
		while (true) {

			if(this.myPlayer.getHP()<=0)
			{
				JOptionPane.showMessageDialog(this.thisGame, "You are dead.");
				new Query().delete(this.myPlayer.getName());
				System.exit(0);
			}
			
			
			// Get closer to next monster spawn
			counter++;

			// refresh screen, say that the turn can continue
			this.refreshScreen();
			this.endPhase = false;

			for (Item i : this.floors.get(currentFloor).getItemList()) {
				thisGame.placeCharacter(i.getX(), i.getY(), i.getSymbol(),
						Color.RED);
			}

			// Get the key from the user, process events
			KeyEvent e = this.thisGame.getKeyEvent();
			if (e.getKeyCode() >= 37 && e.getKeyCode() <= 40) {
				this.movePlayer(e);
			} else if (e.getKeyCode() == 46) {
				this.goDown();
			} else if (e.getKeyCode() == 44) {
				this.goUp();
			} else if (e.getKeyChar() == 'f') {
				this.pickUpItem();
			}
			else
				this.endPhase = true;

			// If shouldn't continue
			if (this.endPhase == true)
				continue;

			// Spawn a monster if ready
			if (counter >= nextSpawn) {
				this.makeMonster();
				nextSpawn = (int) (Math.random() * 100) + 1;
				counter = 0;
			}

			this.mobTurn();

		}
	}

	// Creates a monster on the map, adds it to the Database
	private void makeMonster() {
		boolean placed = false;

		while (!placed) {
			int x = (int) (Math.random() * 90);
			int y = (int) (Math.random() * 40);
			if (this.floors.get(currentFloor).getMap()[y][x] == '.'
					&& x != this.myPlayer.getX() && y != this.myPlayer.getY()) {

				this.floors.get(currentFloor).addMob(
						new Mob(x, y, this.floors.get(currentFloor).getID(),this.currentFloor));
				placed = true;
			}

		}

	}

	// Goes down a floor
	private void goDown() {

		if (this.floors.get(currentFloor).getMap()[this.myPlayer.getY()][this.myPlayer
				.getX()] == '>') {
			Query q = new Query();
			int temp = q.getFloor(this.myPlayer.getName());
			int temp2 = q.getWorld(this.myPlayer.getName());
			this.currentFloor++;

			// IF needs to generate a new map
			if (this.floors.size() <= this.currentFloor) {

				Random r = new Random();
				char[][] data = gen.getMap();

				this.floors.add(new Chunk(this.currentFloor, data,
						new ArrayList<Mob>()));

				this.floors.get(currentFloor).setID(
						q.genFloor(this.myPlayer.getName(), temp2, temp,
								this.currentFloor));

				q.updateLocation(this.myPlayer.getCreatureID(), this.floors
						.get(currentFloor).getID(), this.myPlayer.getX(),
						this.myPlayer.getY());

				this.placePlayerUp();

				// Create random items for this floor.
				for (int i = 0; i < data[0].length; ++i) {
					for (int j = 0; j < data.length; ++j) {
						if (this.floors.get(currentFloor).getMap()[j][i] == '.'
								&& r.nextFloat() < .005) {
							Item item = new Item();
							switch (r.nextInt(3)) {
							case 0:
								item.setAttackModifier((r.nextInt(3) + 1)
										* currentFloor / 2);
								break;
							case 1:
								item.setDefenseModifier((r.nextInt(3) + 1)
										* currentFloor / 2);
								break;
							case 2:
								item.setHPModifier((r.nextInt(5) + 1)
										* currentFloor / 2);
								break;
							}

							item.setX(i);
							item.setY(j);
							item.setID(q.genItem(floors.get(currentFloor)
									.getID(), i, j, item));

							this.floors.get(currentFloor).addItem(item);
							// this.floors.get( currentFloor ).getMap()[i][j] =
							// item.getSymbol();
							this.thisGame.placeCharacter(i, j,
									item.getSymbol(), Color.red);
						}
					}
				}
			}
			this.endPhase = true;
		}
	}

	// Goes up a floor
	private void goUp() {
		if (this.floors.get(currentFloor).getMap()[this.myPlayer.getY()][this.myPlayer
				.getX()] == '<') {
			if (this.currentFloor != 0) {
				this.currentFloor--;
				this.placePlayerDown();
			}
		}
		this.endPhase = true;

	}

	// Places the player on the Down Stairs
	private void placePlayerDown() {
		for (int x = 0; x < this.floors.get(currentFloor).getMap()[0].length; x++)
			for (int y = 0; y < this.floors.get(currentFloor).getMap().length; y++) {
				if (this.floors.get(currentFloor).getMap()[y][x] == '>') {
					this.myPlayer.setX(x);
					this.myPlayer.setY(y);
				}
			}

	}

	// Monsters take a turn
	private void mobTurn() {
		for (Mob m : this.floors.get(currentFloor).getMobList())
			m.takeTurn(this.floors.get(currentFloor), this.myPlayer.getX(),
					this.myPlayer.getY());
	}

	/*
	 * Refreshes the game screen
	 */
	private void refreshScreen() {
		this.thisGame.placeMap(this.floors.get(currentFloor).getMap());

		this.thisGame.placeCharacter(this.myPlayer.getX(),
				this.myPlayer.getY(), '@', Color.GREEN);

		this.thisGame.placeHUD(this.myPlayer.effectiveAttack(),
				this.myPlayer.effectiveDefense(), this.myPlayer.getMagic(),
				this.myPlayer.effectiveHP(), this.myPlayer.getMP(),
				this.myPlayer.getName(), this.currentFloor);

		for (Mob x : this.floors.get(currentFloor).getMobList()) {
			this.thisGame.placeCharacter(x.getX(), x.getY(), x.getSymbol(),
					x.getColor());
		}
	}

	// Moves the Player
	private void movePlayer(KeyEvent e) {
		int x = 0;
		int y = 0;

		if (e.getKeyCode() == 37) {
			x = -1;
		}

		if (e.getKeyCode() == 38) {
			y = -1;
		}
		if (e.getKeyCode() == 39) {
			x = 1;
		}

		if (e.getKeyCode() == 40) {
			y = 1;
		}

		// check if can move
		if (this.floors.get(currentFloor).getMap()[this.myPlayer.getY() + y][this.myPlayer
				.getX() + x] != '#') {

			boolean monsterThere = false;

			// will fight if runs into monster
			for (Mob m : this.floors.get(currentFloor).getMobList()) {
				if ((this.myPlayer.getX() + x) == m.getX()
						&& (this.myPlayer.getY() + y) == m.getY()) {
					this.thisGame.placeMessage(this.myPlayer.fight(m));
					monsterThere = true;
				}
			}

			// Removes any monsters < 0 HP
			ArrayList<Mob> remover = new ArrayList<Mob>();
			for (Mob m : this.floors.get(currentFloor).getMobList()) {
				if (m.effectiveHP() <= 0) {
					remover.add(m);
					Query q = new Query();
					q.deleteCreature(m.getCreatureID());
					this.thisGame.placeMessage("Killed " + m.getName() + ".");
				}
			}

			this.floors.get(currentFloor).getMobList().removeAll(remover);

			// If nothing else, move
			if (!monsterThere) {
				thisGame.placeCharacter(this.myPlayer.getX(), this.myPlayer
						.getY(),
						this.floors.get(currentFloor).getMap()[this.myPlayer
								.getY()][this.myPlayer.getX()], Color.WHITE);

				this.myPlayer.setX(this.myPlayer.getX() + x);
				this.myPlayer.setY(this.myPlayer.getY() + y);

				thisGame.placeCharacter(this.myPlayer.getX(),
						this.myPlayer.getY(), '@', Color.GREEN);
			} else {
				// this.thisGame.placeMessage("A monster is here.");
				// this.refreshScreen();
				// this.endPhase = true;
			}

		} else {
			this.thisGame.placeMessage("A wall is here.");
			this.refreshScreen();
			this.endPhase = true;
		}
	}

	// Change to Stored Procs later
	private void initFromDatabase() {

		boolean newGame = false;
		// Gets players, player selection
		Query q = new Query();
		ArrayList<Player> list = q.getPlayers();
		ArrayList<String> names = new ArrayList<String>();

		for (SQLObject o : list)
			names.add(((Player) o).getName());

		ArrayList<String> deleter = new ArrayList<String>();
		deleter.addAll(names);
		names.add("Delete");
		names.add("Add New");
		String playerName = new String();
		try{
		playerName = names.get(JOptionPane.showOptionDialog(null,
				"Select your character.", "Character Selection",
				JOptionPane.DEFAULT_OPTION, JOptionPane.QUESTION_MESSAGE, null,
				names.toArray(), 0));
		}
		catch(Exception e)
		{
			System.exit(0);	
		}
		// Player deletion
		
		if (playerName.equals("Delete")) {
			String toDelete = names.get(JOptionPane.showOptionDialog(null,
					"Select to delete.", "Character Deletion",
					JOptionPane.DEFAULT_OPTION, JOptionPane.QUESTION_MESSAGE,
					null, deleter.toArray(), 0));
			q.delete(toDelete);
			System.exit(0);

			// ADD new player
		} else if (playerName.equals("Add New")) {
			newGame= true;
			playerName = Database_Init.addPlayer();
			list = q.getPlayers();
			for (SQLObject o : list)
				if (((Player) o).getName().equals(playerName))
					myPlayer = (Player) o;

		}

		else
			for (SQLObject o : list) {
				if (((Player) o).getName().equals(playerName))
					myPlayer = (Player) o;
			}

		// set floor ID.

		this.floors.get(currentFloor)
				.setID(q.getFloor(this.myPlayer.getName()));
		try {
			this.floors.get(currentFloor).retrieveMap(this.myPlayer.getName());
		} catch (IOException e) {
			
			if(newGame)
			{
				this.floors.get(currentFloor).setMap(gen.getMap());
			placePlayerUp();
			}
			else
			{
			JOptionPane.showMessageDialog(this.thisGame, "Incorrect Computer for Player.");
			System.exit(0);
			}
		}

		int counter = q.getNext(this.floors.get(this.currentFloor).getID());
		int floor = currentFloor;

		// Traverses DOWN the map
		while (counter != -1 && counter != 0) {

			floor++;
			this.floors.add(new Chunk(floor, new char[40][90],
					new ArrayList<Mob>()));
			this.floors.get(floor).setID(counter);
			try {
				this.floors.get(floor).retrieveMap(this.myPlayer.getName());
			} catch (IOException e) {
				e.printStackTrace();
			}

			counter = q.getNext(this.floors.get(floor).getID());
		}

		// TRAVERSES UP THE MAP
		floor = 0;
		counter = q.getPrev(this.floors.get(this.currentFloor).getID());
		while (counter != -1 && counter != 0) {

			floor++;
			this.floors.add(0, new Chunk(floor, new char[40][90],
					new ArrayList<Mob>()));
			this.floors.get(0).setID(counter);
			try {
				this.floors.get(0).retrieveMap(this.myPlayer.getName());
			} catch (IOException e) {
				e.printStackTrace();
			}

			counter = q.getPrev(this.floors.get(0).getID());
		}
		this.currentFloor += floor;

		// Get the Mobs and items that are on the chunks.
		for (Chunk c : this.floors) {
			c.getMobList().addAll(q.getMobsForChunk(c.getID()));
			c.getItemList().addAll(q.getItemsForChunk(c.getID()));
		}
		
		// Get the Player's items.
		for( Item i : q.getCreatureItems( myPlayer.getCreatureID() ) )
			myPlayer.giveItem( i );

	}

	// Saves Data afterwards
	private void saveTables() {
		Query q = new Query();
		q.updateLocation(this.myPlayer.getCreatureID(),
				this.floors.get(currentFloor).getID(), this.myPlayer.getX(),
				this.myPlayer.getY());
		q.updateCreature(this.myPlayer.getCreatureID(),
				this.myPlayer.getLevel(), this.myPlayer.getHP(),
				this.myPlayer.getMP(), this.myPlayer.getAttack(),
				this.myPlayer.getDefense(), this.myPlayer.getMagic());

		for (Chunk c : this.floors) {
			for (Mob m : c.getMobList()) {
				q.updateLocation(m.getCreatureID(), c.getID(), m.getX(),
						m.getY());
				q.updateCreature(m.getCreatureID(), m.getLevel(), m.getHP(),
						m.getMP(), m.getAttack(), m.getDefense(), m.getMagic());
			}
			try {
				c.writeMap(this.myPlayer.getName());
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		q.updateFloors(q.getWorld(this.myPlayer.getName()), this.floors.size());
	}

	public static void main(String[] args) {
		GameEngine newGame = new GameEngine();
		newGame.gameLoop();
	}
}
